// 从 'websocket' 库中导入 w3cwebsocket 类和接口
import { w3cwebsocket, IMessageEvent, ICloseEvent } from 'websocket';
// 从 'buffer' 库中导入 Buffer 类
import { Buffer } from 'buffer';

// 定义 Ping 消息，用于 WebSocket 心跳检测
export const Ping = new Uint8Array([0, 100, 0, 0, 0, 0])
// 定义 Pong 消息，通常作为 Ping 消息的响应
export const Pong = new Uint8Array([0, 101, 0, 0, 0, 0])

// 心跳间隔时间设置为10秒
const heartbeatInterval = 10 // seconds

// 定义一个异步函数 sleep，用于暂停执行指定的秒数
export let sleep = async (second: number): Promise<void> => {
    return new Promise((resolve, _) => {
        setTimeout(() => {
            resolve() // 在指定的秒数后解析这个 Promise
        }, second * 1000) // 将秒数转换为毫秒
    })
}

// 定义一个枚举 Ack，用于表示不同的登录状态
export enum Ack {
    Success = "Success", // 登录成功
    Timeout = "Timeout", // 登录超时
    Loginfailed = "LoginFailed", // 登录失败
    Logined = "Logined", // 已登录
}

// 定义一个异步函数 doLogin，用于执行 WebSocket 的登录操作
export let doLogin = async (url: string): Promise<{ status: string, conn: w3cwebsocket }> => {
    const LoginTimeout = 5 // 设置登录超时时间为5秒
    return new Promise((resolve, reject) => {
        let conn = new w3cwebsocket(url) // 创建一个新的 WebSocket 连接
        conn.binaryType = "arraybuffer" // 设置二进制数据类型为 'arraybuffer'

        // 设置一个登录超时定时器
        let tr = setTimeout(() => {
            resolve({ status: Ack.Timeout, conn: conn }); // 如果超时，则解析 Promise 为 Timeout 状态
        }, LoginTimeout * 1000); // 登录超时时间

        conn.onopen = () => { // 当 WebSocket 连接打开时
            console.info("websocket open - readyState:", conn.readyState) // 打印连接状态

            if (conn.readyState === w3cwebsocket.OPEN) { // 如果连接状态为 OPEN
                clearTimeout(tr) // 清除超时定时器
                resolve({ status: Ack.Success, conn: conn }); // 解析 Promise 为 Success 状态
            }
        }
        conn.onerror = (error: Error) => { // 当 WebSocket 连接出错时
            clearTimeout(tr) // 清除超时定时器
            // console.debug(error) // 可选地打印错误信息
            resolve({ status: Ack.Loginfailed, conn: conn }); // 解析 Promise 为 Loginfailed 状态
        }
    })
}

export enum State {
    INIT,
    CONNECTING,
    CONNECTED,
    RECONNECTING,
    CLOSEING,
    CLOSED,
}


// 定义 IMClient 类
export class IMClient {
    wsurl: string  // WebSocket 服务器的 URL
    state = State.INIT  // 客户端的初始状态
    private conn: w3cwebsocket | null  // WebSocket 连接对象
    private lastRead: number  // 记录最后一次读取消息的时间

    // 类构造函数，初始化 WebSocket URL 和用户信息
    constructor(url: string, user: string) {
        this.wsurl = `${url}?user=${user}`  // 构造带用户信息的 WebSocket URL
        this.conn = null  // 初始化连接为 null
        this.lastRead = Date.now()  // 设置最后读取时间为当前时间
    }

    // 登录方法
    async login(): Promise<{ status: string }> {
        if (this.state == State.CONNECTED) {  // 如果已经连接，则直接返回登录状态
            return { status: Ack.Logined }
        }
        this.state = State.CONNECTING  // 设置状态为连接中

        let { status, conn } = await doLogin(this.wsurl)  // 尝试登录
        console.info("login - ", status)  // 打印登录状态

        if (status !== Ack.Success) {  // 如果登录不成功，返回当前状态
            return { status }
        }

        // 设置 WebSocket 消息处理函数
        conn.onmessage = (evt: IMessageEvent) => {
            try {
                this.lastRead = Date.now()  // 更新最后读取时间

                let buf = Buffer.from(<ArrayBuffer>evt.data)  // 将消息数据转换为 Buffer
                let command = buf.readInt16BE(0)  // 读取命令字节
                let len = buf.readInt32BE(2)  // 读取数据长度
                console.info(`<<<< received a message ; command:${command} len: ${len}`)  // 打印消息信息
                if (command == 101) {  // 如果收到的是 Pong 响应
                    console.info("<<<< received a pong...")  // 打印 Pong 消息
                }
            } catch (error) {
                console.error(evt.data, error)  // 打印错误信息
            }
        }

        // 设置 WebSocket 错误处理函数
        conn.onerror = (error) => {
            console.info("websocket error: ", error)  // 打印错误信息
            this.errorHandler(error)  // 调用错误处理函数
        }

        // 设置 WebSocket 连接关闭处理函数
        conn.onclose = (e: ICloseEvent) => {
            console.debug("event[onclose] fired")  // 打印关闭事件
            if (this.state == State.CLOSEING) {  // 如果是主动关闭状态
                this.onclose("logout")  // 调用关闭处理函数
                return
            }
            this.errorHandler(new Error(e.reason))  // 调用错误处理函数
        }

        this.conn = conn  // 保存 WebSocket 连接
        this.state = State.CONNECTED  // 更新状态为已连接

        this.heartbeatLoop()  // 启动心跳检测循环
        this.readDeadlineLoop()  // 启动读超时检测循环

        return { status }  // 返回登录状态
    }

    // 登出方法
    logout() {
        if (this.state === State.CLOSEING) {  // 如果已经在关闭状态，直接返回
            return
        }
        this.state = State.CLOSEING  // 设置状态为关闭中
        if (!this.conn) {  // 如果没有连接，直接返回
            return
        }
        console.info("Connection closing...")  // 打印关闭连接信息
        this.conn.close()  // 关闭 WebSocket 连接
    }

    // 心跳检测循环方法
    private heartbeatLoop() {
        console.debug("heartbeatLoop start")  // 打印心跳循环开始信息

        let loop = () => {
            if (this.state != State.CONNECTED) {  // 如果不是连接状态，退出循环
                console.debug("heartbeatLoop exited")
                return
            }

            console.log(`>>> send ping ; state is ${this.state},`)  // 打印发送 Ping 消息信息
            this.send(Ping)  // 发送 Ping 消息

            setTimeout(loop, heartbeatInterval * 1000)  // 设置下一次心跳检测
        }
        setTimeout(loop, heartbeatInterval * 1000)  // 启动心跳检测循环
    }

    // 读超时检测循环方法
    private readDeadlineLoop() {
        console.debug("deadlineLoop start")  // 打印读超时循环开始信息
        let loop = () => {
            if (this.state != State.CONNECTED) {  // 如果不是连接状态，退出循环
                console.debug("deadlineLoop exited")
                return
            }
            if ((Date.now() - this.lastRead) > 3 * heartbeatInterval * 1000) {  // 如果超过读超时时间
                this.errorHandler(new Error("read timeout"))  // 调用错误处理函数
            }
            setTimeout(loop, 1000)  // 设置下一次检测
        }
        setTimeout(loop, 1000)  // 启动读超时检测循环
    }

    // 连接关闭处理方法
    private onclose(reason: string) {
        console.info("connection closed due to " + reason)  // 打印连接关闭原因
        this.state = State.CLOSED  // 更新状态为已关闭
        // 通知上层应用连接已关闭，这里省略具体实现
    }

    // 错误处理和自动重连方法
    private async errorHandler(error: Error) {
        if (this.state == State.CLOSED || this.state == State.CLOSEING) {  // 如果是关闭状态，不进行重连
            return
        }
        this.state = State.RECONNECTING  // 设置状态为重连中
        console.debug(error)  // 打印错误信息
        for (let index = 0; index < 10; index++) {  // 尝试重连10次
            try {
                console.info("try to relogin")  // 打印重连尝试信息
                let { status } = await this.login()  // 尝试登录
                if (status == "Success") {  // 如果登录成功，退出重连
                    return
                }
            } catch (error) {
                console.warn(error)  // 打印重连错误信息
            }
            await sleep(5)  // 等待一段时间后再次尝试重连
        }
        this.onclose("reconnect timeout")  // 重连失败，调用关闭处理方法
    }

    // 发送消息方法
    private send(data: Buffer | Uint8Array): boolean {
        try {
            if (this.conn == null) {  // 如果没有连接，返回失败
                return false
            }
            this.conn.send(data)  // 发送数据
        } catch (error) {
            this.errorHandler(new Error("read timeout"))  // 发送失败，调用错误处理函数
            return false  // 返回发送失败
        }
        return true  // 返回发送成功
    }
}

